from twisted.protocols import basic
import xml.dom.minidom
from xml.dom.minidom import Node
from xml.dom.minidom import Element
import imp
import Config, Event, Room
from Log import *
from Utils import *

class BlinkUser(basic.LineOnlyReceiver) : 
    # static variable
    XML_TERMINATOR = '\0'           
    maxConnection =  int( Config.get("blink.max_connection", "10")  )
    _usersCount = 0
    _usersList = {}
    _mytag = "user"

    #interface for twisted, 
    def connectionMade(self) : 
        if self._usersCount >= self.maxConnection :
            self.denyUser("max connection exceed.") 
            return;
        self._usersCount+=1;
        self._username = None
        self._receivedBuffer = "" 
        
        self.max_line_length = int( Config.get("blink.xml_max_length" , "512") )
        self.max_xml_length = int( Config.get("blink.xml_max_length" , "2048") )
        self.MAX_LENGTH = self.max_line_length # for twisted
        
        self.room = None
        self.plugins = {}
        self.events = Event.EventDispatcher(self)
        
        self.loadPlugin("login")
        dbg("user connected ")

    def disconnect( self, reason = "no reason") : 
        dbg("user disconnected ,"+reason)
        self.transport.loseConnection()
       
    def acceptUser(self, uname) :
        self._username = uname
        self._usersList[self._username] = self

        node = Element( "login" )
        node.attributes['result'] = 'pass'

        u = Element( "user" )
        u.appendChild( node )

        resp = newxml(  )
        resp.firstChild.appendChild( u )
        
        self.sendXml( resp )
        
        
        Room.BlinkRoom.commonRoom._addUser(self)
        self.events.addListener('xml.user', self.doUserNode)
        
        dbg("verify pass")
    
    def denyUser(self, reason="Login failed") : 
        node = Element( "login" )
        node.attributes['result'] = 'failed'
        node.attributes['reason'] = reason
        
        
        u = Element( "user" )
        u.appendChild( node )

        resp = newxml(  )
        resp.firstChild.appendChild( u )
        
        self.sendXml( resp )
        dbg(resp.toxml())
        self.disconnect(reason)
        dbg("verify failed."+reason)
        
    def verifyUser(self, node):
        dbg("verify user")
        loginNode = node.firstChild
        
        if (node.nodeName!='user') or (loginNode==None) or (loginNode.nodeName != 'login') :
            self.denyUser("login first." )
            return
            
        uname = loginNode.getAttribute("name") 
            
        if (uname==None) or (uname=="") : 
            self.denyUser("missing username.")
            return
            
        if self._usersList.has_key( uname ) :
            self.denyUser("duplicate username, user already logged in.")
            return  

        e = Event.Event( {'xmlNode': loginNode , 'result':'pass', 'reason':''} ) 
        self.events.dispatch('login',e )
        
        if e.args['result'] == 'pending' :
            # Verifying by plug-in, not deny or accept yet, Plug-in will make a decision this later.
            return
        elif e.args['result'] == 'failed' :
            self.denyUser("login failed, "+e.args['reason'])
            return
        elif e.args['result'] == 'pass' :
            pass
        else :
            warn("unknown login result : "+e.args['result']+", let's user pass by default. ")
        self.acceptUser(uname)    
            
    
    def connectionLost(self, reason) : 
        self._usersCount -= 1
        self._onDisconnect( reason )
        if self._usersList.has_key(self._username) :
            del self._usersList[self._username] 

    def lineLengthExceeded(self, line) :
        self.disconnect("xml line length exceed!")

    def dataReceived(self, data):
        # force twisted to split after XML_TERMINATOR charactor.
        data = data.replace(self.XML_TERMINATOR , self.XML_TERMINATOR+'\r\n')
        basic.LineOnlyReceiver.dataReceived(self,data)
            
    def lineReceived(self, line) : 
        self._receivedBuffer += "\n"+line
        
        if len(self._receivedBuffer) > self.max_xml_length  :
            self._receivedBuffer  = ""
            self.disconnect("xml size overlimit.")
        
        if len(line)>0 and line[-1]== self.XML_TERMINATOR: 
            self._onXml( self._receivedBuffer [:-1] )
            self._receivedBuffer  = ""

               
    #internal operation
    def _onXml(self,xmlTxt) : 
        if xmlTxt=='' : return 
        
        dbg("receive : "+ xmlTxt)
        try :
            xmlDoc = xml.dom.minidom.parseString(  xmlTxt  ) 
        except Exception, e: 
            err("cant parse : "+ xmlTxt+' : '+str(e))
            return
            
        if xmlDoc.firstChild.nodeName=='policy-file-request' :
            f = open( 'policy.xml')
            self.sendXml( xml.dom.minidom.parseString(  f.read()  ) )
            f.close()
            return 
            
        for node in xmlDoc.firstChild.childNodes : 
            if self._username==None : # non-verified user
                self.verifyUser(node)
            else :
                dbg("dispatching : "+ "xml."+node.nodeName)
                self.events.dispatch("xml."+node.nodeName,  Event.Event( {'xmlNode':node} ))
    
    def doUserNode(self, evt) :
        pass

    def _onDisconnect(self,reason) : 
        if self.room :
            self.room._removeUser(self.getUsername())
            
    def sendXml( self,xmlDoc ) : 
        self.transport.write( str(xmlDoc.toxml().encode("utf-8")+"\r\n"+self.XML_TERMINATOR ) )
    
    def getUsername( self ) : 
        return self._username
        
    def setUsername( self,name ) : 
        if self._usersList.has_key( name ) :
            return False,"username using by other."
        
        del self._usersList[self._username]
        self._username = name
        self._usersList[name] = self
        return True,""
    
    def changeRoom(self,toroom) : 
        self.events.dispatch("onChangeRoom",  Event.Event( {'toroom':toroom} ) )
        
        if self.room!=None :
            self.room._removeUser( self.getUsername() )
        toroom._addUser( self )
        
    
    def getXmlInfo(self) : 
        uElm = Element("user")
        uElm.setAttribute( "name", self.getUsername());
        return uElm
                    
    def loadPlugin(self, pluginName) :
        # find module file from specific directory
        try :
            file, filename, description = imp.find_module( pluginName, [Config.get("blink.plugin_path")])
            # load module
            pluginmodule = imp.load_module(pluginName, file, filename, description)
            pluginClass = getattr(pluginmodule, pluginName)
            self.plugins[pluginName] =  pluginClass(self) 
            self.events.dispatch('loadplugin', Event.Event( {'plugin': pluginName} ) )
            dbg("user load plugin ok: "+pluginName)
        except Exception ,e: 
            err("user load plugin failed : "+pluginName+", "+str(e))


 